#pragma warning disable 168

using System;

namespace System
{
	extension Event<T>
	{
		public implicit void operator+=(T dlg) mut
		{
			Add(dlg);
		}
	}
}

namespace Tests
{
	class Delegates
	{
		struct Valueless
		{
			public int MutMethod(int a) mut
			{
				return 210 + a;
			}

			public int NonMutMethod(int a)
			{
				return 210 + a;
			}
		}

		struct Splattable
		{
			public int32 mA = 10;
			public int16 mB = 200;

			public int MutMethod(int a) mut
			{
				mB += 100;
				return mA + mB + a;
			}

			public int NonMutMethod(int a)
			{
				return mA + mB + a;
			}

			public void TestLambda() mut
			{
				delegate int(ref int a, ref int b) dlg = scope [&] (a, b) => 
				{
					a += 20;
					b += 30;
					mA++;
					return mA + a + b;
				};

				mA = 100;
				int testA = 8;
				int testB = 9;
				Test.Assert(dlg(ref testA, ref testB) == 100+1 + 20+8 + 30+9);
				Test.Assert(testA == 28);
				Test.Assert(testB == 39);
				Test.Assert(mA == 101);
			}
		}

		struct NonSplattable
		{
			public int32 mA = 10;
			public int16 mB = 200;
			public int64 mC = 300;
			public int64 mD = 400;

			public int MutMethod(int a) mut
			{
				mB += 100;
				return mA + mB + a;
			}

			public int NonMutMethod(int a)
			{
				return mA + mB + a;
			}
		}

		class ClassA
		{
			public int mA;

			public void TestLambda()
			{
				delegate int(ref int a, ref int b) dlg = scope (a, b) => 
				{
					a += 20;
					b += 30;
					mA++;
					return mA + a + b;
				};

				mA = 100;
				int testA = 8;
				int testB = 9;
				Test.Assert(dlg(ref testA, ref testB) == 100+1 + 20+8 + 30+9);
				Test.Assert(testA == 28);
				Test.Assert(testB == 39);
				Test.Assert(mA == 101);
			}
		}

		class ClassB<T>
		{
			public delegate int DelegateB(T val);
		}

		[CRepr]
		struct Vector3f
		{
			public float mX;
			public float mY;
		}

		static Vector3f GetVector3f()
		{
			return .() { mX = 101, mY = 102 };
		}

		[Test]
		public static void TestBasics()
		{
			delegate int(int a) dlg;

			Valueless val0 = .();
			dlg = scope => val0.MutMethod;
			Test.Assert(dlg(9) == 219);
			dlg = scope => val0.NonMutMethod;
			Test.Assert(dlg(9) == 219);

			Splattable val1 = .();
			dlg = scope => val1.MutMethod;
			Test.Assert(dlg(9) == 319);
			dlg = scope => val1.NonMutMethod;
			Test.Assert(dlg(9) == 219);
			Test.Assert(val1.mB == 200);

			dlg = scope => (ref val1).MutMethod;
			Test.Assert(dlg(9) == 319);
			dlg = scope => (ref val1).NonMutMethod;
			Test.Assert(dlg(9) == 319);
			Test.Assert(val1.mB == 300);

			NonSplattable val2 = .();
			dlg = scope => val2.MutMethod;
			Test.Assert(dlg(9) == 319);
			dlg = scope => val2.NonMutMethod;
			Test.Assert(dlg(9) == 219);
			Test.Assert(val2.mB == 200);

			dlg = scope => (ref val2).MutMethod;
			Test.Assert(dlg(9) == 319);
			dlg = scope => (ref val2).NonMutMethod;
			Test.Assert(dlg(9) == 319);
			Test.Assert(val2.mB == 300);

			val1.TestLambda();
			ClassA ca = scope .();
			ca.TestLambda();

			ClassB<int8>.DelegateB dlg2 = scope (val) => val + 123;
			Test.Assert(dlg2(3) == 126);

			void LocalEventHandler(Object sender, EventArgs e)
			{

			}

			Event<EventHandler> e = .();
			e += new (sender, e) => {};
			e += new => LocalEventHandler;
			e.Dispose();

			delegate Vector3f() vecDlg = scope => GetVector3f;
			Test.Assert(vecDlg().mX == 101);

			int allocCount = 0;

			Action act = null;
			for (int i = 0; i < 10; i++)
			{
				int j = i / 3;
			    Action newAct = scope:: () =>
			    {
			        Console.WriteLine($"act {j}");
			    };

			    if (act == null || act != newAct)
			    {
			        act = newAct;
			        allocCount++;
			    }
				else
				{
					Test.Assert(act.GetHashCode() == newAct.GetHashCode());
				}
			}
			Test.Assert(allocCount == 4);
		}

		public static void Modify(ref int a, ref Splattable b)
		{
			a += 1000;
			b.mA += 2000;
			b.mB += 3000;
		}

		[Test]
		public static void TestRefs()
		{
			delegate void(ref int a, ref Splattable b) dlg = scope => Modify;

			int a = 123;
			Splattable splat = .();
			splat.mA = 234;
			splat.mB = 345;

			dlg(ref a, ref splat);
			Test.Assert(a == 1123);
			Test.Assert(splat.mA == 2234);
			Test.Assert(splat.mB == 3345);
		}

		public static void TestCasting()
		{
			delegate int(int, int) dlg0 = scope (a, b) => 1;
			delegate int(int a, int b) dlg1 = dlg0;
			delegate int(int a2, int b2) dlg2 = (.)dlg1;
			delegate int(float a, float b) dlg3 = scope (a, b) => 2;

			Object obj = dlg0;
			dlg1 = (.)obj;
			dlg2 = (.)obj;
			Test.Assert(obj is delegate int(int a, int b));
			Test.Assert(!(obj is delegate int(float a, float b)));

			function int(int, int) func0 = null;
			function int(int a, int b) func1 = func0;
			function int(int a2, int b2) func2 = (.)func1;
		}
	}
}
